{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Layout and Styling of Jupyter widgets\n",
    "\n",
    "This section presents how to layout and style Jupyter interactive widgets to build rich and *reactive* widget-based applications.\n",
    "\n",
    "Every Jupyter widget has two attributes to customize layout and styling of the widget. They are `layout` and `style` attributes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The `style` attribute\n",
    "\n",
    "The `style` attribute is used to expose non-layout related styling attributes of widgets. For most widgets, the only style that can be modified is `description_width`, which is the width of the description label for the widget.\n",
    "\n",
    "However, a few widgets have additional style settings, as described below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Style Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Button, ButtonStyle\n",
    "b2 = Button(description='Custom color', style=dict(button_color='lightgreen'))\n",
    "b2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b2.style.button_color = 'yellow'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can get a list of the style attributes for a widget with the `keys` property."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b2.style.keys"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Like the `layout` attribute, widget styles can be assigned to other widgets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b3 = Button(description='Another button', style=b2.style)\n",
    "b3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b2.style.button_color = 'red'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Widget styling attributes are specific to each widget type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import IntSlider\n",
    "s1 = IntSlider(description='Blue handle')\n",
    "s1.style.handle_color = 'lightblue'\n",
    "s1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is a [list of all style keys](Table_of_widget_keys_and_style_keys.ipynb#Style-keys)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### `button_style` and `bar_style` attributes\n",
    "These attributes let you style some widgets with pre-defined settings that are `theme aware`. These properties affect both background color and text color of widgets. These attributes are available for the widgets listed below. Available options for these styles are `success`, `info`, `warning`, `danger`. Buttons also have option `primary`\n",
    "- **button_style** is available for: Button, ToggleButton, ToggleButtons, FileUpload\n",
    "- **bar_style** is available for: FloatProgress, IntProgress"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b4 = Button(description='Yet another button')\n",
    "b4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b4.button_style = 'warning'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that setting the `style` of a button overrides the `button_style`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b4.style.button_color = 'red'  # Makes the color red\n",
    "b4.button_style = 'success'  # Does not turn the color green because the color has been explicitly set in the style"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `layout` attribute: the foundation of widget layout\n",
    "\n",
    "Jupyter interactive widgets have a `layout` attribute exposing a number of CSS properties that impact how widgets are laid out.\n",
    "\n",
    "### Exposed CSS properties\n",
    "\n",
    "<div class=\"alert alert-info\" style=\"margin: 20px\">\n",
    "The following properties map to the values of the CSS properties of the same name (underscores being replaced with dashes), applied to the top DOM elements of the corresponding widget.\n",
    "</div>\n",
    "\n",
    "\n",
    "<details>\n",
    "<summary><strong>Sizes</strong></summary>\n",
    "\n",
    "- `height`\n",
    "- `width`\n",
    "- `max_height`\n",
    "- `max_width`\n",
    "- `min_height`\n",
    "- `min_width`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><strong>Display</strong></summary>\n",
    "\n",
    "- `visibility`\n",
    "- `display`\n",
    "- `overflow`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><strong>Box model</strong></summary>\n",
    "\n",
    "- `border` -- changing in `ipywidgets 8`; will have four new properties `border_left`, `border_right`, `border_top` and `border_bottom`, to better match what CSS provides. `border` will still be available as a shortcut, but will not set a `border` CSS property.\n",
    "- `margin`\n",
    "- `padding`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><strong>Positioning</strong></summary>\n",
    "\n",
    "- `top`\n",
    "- `left`\n",
    "- `bottom`\n",
    "- `right`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><strong>Image/media</strong></summary>\n",
    "\n",
    "- `object_fit`\n",
    "- `object_position`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><strong>Flexbox</strong></summary>\n",
    "\n",
    "- `order`\n",
    "- `flex_flow`\n",
    "- `align_items`\n",
    "- `flex`\n",
    "- `align_self`\n",
    "- `align_content`\n",
    "- `justify_content`\n",
    "- `justify_items`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><strong>Grid layout</strong></summary>\n",
    "\n",
    "- `grid_auto_columns`\n",
    "- `grid_auto_flow`\n",
    "- `grid_auto_rows`\n",
    "- `grid_gap`\n",
    "- `grid_template_rows`\n",
    "- `grid_template_columns`\n",
    "- `grid_template_areas`\n",
    "- `grid_row`\n",
    "- `grid_column`\n",
    "- `grid_area`\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Shorthand CSS properties\n",
    "\n",
    "You may have noticed that certain CSS properties such as `margin-[top/right/bottom/left]` seem to be missing. The same holds for `padding-[top/right/bottom/left]` etc.\n",
    "\n",
    "In fact, you can atomically specify `[top/right/bottom/left]` margins via the `margin` attribute alone by passing the string `'100px 150px 100px 80px'` for a respectively `top`, `right`, `bottom` and `left` margins of  `100`, `150`, `100` and `80` pixels.\n",
    "\n",
    "Similarly, the `flex` attribute can hold values for `flex-grow`, `flex-shrink` and `flex-basis`. The `border` attribute is a shorthand property for `border-width`, `border-style (required)`, and `border-color`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `Layout` example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following example shows how to resize a `Button` so that its views have a height of `80px` and a width of `50%` of the available space. It also includes an example of setting a CSS property that requires multiple values (a border, in this case):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Button, Layout\n",
    "\n",
    "b1 = Button(description='(50% width, 80px height) button',\n",
    "           layout=Layout(width='50%', height='80px', border='2px dotted blue'))\n",
    "b1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `layout` property can be shared between multiple widgets and assigned directly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Button(description='Another button with the same layout', layout=b1.layout)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b1.layout.width = '30%'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise\n",
    "\n",
    "In the cell below, make the button's border solid and green and make its width 70 pixels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b1.layout. # fill this in, might take more than one line\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Container Layout Widgets\n",
    "- ### Flexbox CSS layout based\n",
    "    The *Flexbox* CSS specification is great for laying out items in a single direction, horizontally or vertically. Two dimensional layout can be done with flexbox by using a combination of horizontal and vertical components with some limitations. A notebook with more details about [widgets and the Flexbox model](reference_guides/guide-flex-box.ipynb) is available.\n",
    "    - **Box**: Base container widget for creating Flexbox layout\n",
    "        - **HBox**: Container widget with horizontal Flexbox layout\n",
    "        - **VBox**: Container widget with vertical Flexbox layout\n",
    "- ### Grid CSS layout based\n",
    "    The *Grid* CSS specification is designed to be used for two dimensional layout. There are properties for specifying the number of items in each row or column, how they should be sized, and how items should be aligned.\n",
    "    A notebook with more details about [widgets and the Grid model](reference_guides/guide-grid-box.ipynb) is available.\n",
    "    - **GridBox**: Base container widget for creating Grid layout\n",
    "        - **TwoByTwoLayout**: A layout with 2 x 2 grid\n",
    "        - **AppLayout**: Application like layout with header, footer and sidebars\n",
    "        - **GridspecLayout**: m x n grid layout\n",
    "\n",
    "#### For more information about Flexbox and Grid\n",
    "\n",
    "If you want to learn more about CSS layout after this tutorial, take a look at this [excellent set of articles on CSS layout at MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout). The Flexbox and Grid articles each have links to more extensive guides at the end of the article."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Flexbox Layout\n",
    "\n",
    "The `HBox` and `VBox` classes are special cases of the `Box` widget.\n",
    "\n",
    "The `Box` widget enables the entire CSS flexbox spec as well as the Grid layout spec, enabling rich reactive layouts in the Jupyter notebook. It aims at providing an efficient way to lay out, align and distribute space among items in a container.\n",
    "\n",
    "Again, the whole flexbox spec is exposed via the `layout` attribute of the container widget (`Box`) and the contained items. One may share the same `layout` attribute among all the contained items.\n",
    "\n",
    "We will revisit more of the flexbox spec later in this tutorial. For now, let's look at a couple of examples using `VBox` and `HBox`.\n",
    "\n",
    "### The VBox and HBox helpers\n",
    "\n",
    "The `VBox` and `HBox` helper classes provide simple defaults to arrange child widgets in vertical and horizontal boxes.\n",
    "\n",
    "### HBox and VBox Examples\n",
    "\n",
    "Most of the core-widgets have default heights and widths that tile well together. This allows simple layouts based on the `HBox` and `VBox` helper functions to align naturally.\n",
    "\n",
    "#### Four buttons in a VBox. Items stretch to the maximum width, in a vertical box taking `50%` of the available space."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Layout, Button, VBox\n",
    "\n",
    "items_layout = Layout(width='auto')     # override the default width of the button to 'auto' to let the button grow\n",
    "\n",
    "box_layout = Layout(border='solid',\n",
    "                    width='50%')\n",
    "\n",
    "words = ['correct', 'horse', 'battery', 'staple']\n",
    "items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]\n",
    "box = VBox(children=items, layout=box_layout)\n",
    "box"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A reactive form\n",
    "\n",
    "The form is a `VBox` of width '50%'. Each row in the VBox is an HBox, that justifies the content with space between.\n",
    "\n",
    "Note that the labels and interactive elements are nicely aligned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Layout, HBox, VBox, Button, FloatText, Textarea, Dropdown, Label, IntSlider\n",
    "\n",
    "# space-between divides the whitespace evenly between elements\n",
    "form_item_layout = Layout(justify_content='space-between')\n",
    "\n",
    "form_items = [\n",
    "    HBox([Label(value='Storage capacity'), IntSlider(min=4, max=512)], layout=form_item_layout),\n",
    "    HBox([Label(value='Egg style'),\n",
    "         Dropdown(options=['Scrambled', 'Sunny side up', 'Over easy'])], layout=form_item_layout),\n",
    "    HBox([Label(value='Ship size'),\n",
    "         FloatText()], layout=form_item_layout),\n",
    "    HBox([Label(value='Information'),\n",
    "         Textarea()], layout=form_item_layout)\n",
    "]\n",
    "\n",
    "form = VBox(form_items, layout=Layout(\n",
    "    border='2px solid gray', padding='10px',\n",
    "    align_items='stretch', width='50%')\n",
    ")\n",
    "form"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Grid Layout\n",
    "\n",
    "The `GridBox` class is a special case of the `Box` widget.\n",
    "\n",
    "The whole grid layout spec is exposed via the `layout` attribute of the container widget (`Box`) and the contained items. One may share the same `layout` attribute among all the contained items.\n",
    "\n",
    "This tutorial focuses on the higher-level layout options that are based on the grid spec:\n",
    "\n",
    "- **TwoByTwoLayout**: A layout with 2 x 2 grid\n",
    "- **AppLayout**: Application like layout with header, footer and sidebars\n",
    "- **GridspecLayout**: m x n grid layout\n",
    "\n",
    "A more detailed description of the [Grid layout is available](reference_guides/guide-grid-box.ipynb). The [Grid layout guide on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout#Guides) is also excellent."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grid Layout Template Examples\n",
    "\n",
    "#### Some setup\n",
    "\n",
    "The cell below creates several buttons for use in the examples that follow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Utils widgets\n",
    "from ipywidgets import Button, Layout, jslink, IntText, IntSlider\n",
    "\n",
    "def create_expanded_button(description, button_style):\n",
    "    return Button(description=description, button_style=button_style, \n",
    "                  layout=Layout(height='auto', width='auto'))\n",
    "\n",
    "top_left_button = create_expanded_button(\"Top left\", 'info')\n",
    "top_right_button = create_expanded_button(\"Top right\", 'success')\n",
    "bottom_left_button = create_expanded_button(\"Bottom left\", 'danger')\n",
    "bottom_right_button = create_expanded_button(\"Bottom right\", 'warning')\n",
    "\n",
    "top_left_text = IntText(description='Top left', layout=Layout(width='auto', height='auto'))\n",
    "top_right_text = IntText(description='Top right', layout=Layout(width='auto', height='auto'))\n",
    "bottom_left_slider = IntSlider(description='Bottom left', layout=Layout(width='auto', height='auto'))\n",
    "bottom_right_slider = IntSlider(description='Bottom right', layout=Layout(width='auto', height='auto'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TwoByTwoLayout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can easily create a layout with 4 widgets arranged in a 2x2 grid using the `TwoByTwoLayout` widget:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**2x2 Grid**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import TwoByTwoLayout\n",
    "\n",
    "TwoByTwoLayout(top_left=top_left_button,\n",
    "               top_right=top_right_button,\n",
    "               bottom_left=bottom_left_button,\n",
    "               bottom_right=bottom_right_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighboring cells"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TwoByTwoLayout(top_left=top_left_button,\n",
    "               bottom_left=bottom_left_button,\n",
    "               bottom_right=bottom_right_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can pass `merge=False` in the argument of the `TwoByTwoLayout` constructor if you don't want this behavior"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout_2x2 = TwoByTwoLayout(top_left=top_left_button,\n",
    "               bottom_left=bottom_left_button,\n",
    "               bottom_right=bottom_right_button,\n",
    "               merge=False)\n",
    "layout_2x2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can access the widgets in the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout_2x2.bottom_right.button_style = 'primary'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can add a missing widget even after the layout initialization:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout_2x2.top_right = top_right_button"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout_2x2.grid_gap = '10px'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### bqplot Figure with linked sliders"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can easily create more complex layouts with custom widgets. For example, you can use a [bqplot](https://github.com/bqplot/bqplot) Figure widget to add plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import bqplot as bq\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "size = 100\n",
    "np.random.seed(0)\n",
    "\n",
    "x_data = range(size)\n",
    "y_data = np.random.randn(size)\n",
    "y_data_2 = np.random.randn(size)\n",
    "y_data_3 = np.cumsum(np.random.randn(size) * 100.)\n",
    "\n",
    "x_ord = bq.OrdinalScale()\n",
    "y_sc = bq.LinearScale()\n",
    "\n",
    "bar = bq.Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})\n",
    "ax_x = bq.Axis(scale=x_ord)\n",
    "ax_y = bq.Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')\n",
    "\n",
    "fig = bq.Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025,\n",
    "                layout=Layout(width='auto', height='90%'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import FloatSlider\n",
    "\n",
    "max_slider = FloatSlider(min=0, max=10, default_value=2, description=\"Max: \",\n",
    "                         layout=Layout(width='auto', height='auto'))\n",
    "min_slider = FloatSlider(min=-1, max=10, description=\"Min: \",\n",
    "                         layout=Layout(width='auto', height='auto'))\n",
    "\n",
    "# Set up two sliders and a graph. The \"max\" slider sets the upper limit for the \"min\" \n",
    "# slider, and the value of the \"min\" slider sets the upper limit for the \"max\" slider.\n",
    "bqfig = TwoByTwoLayout(top_left=min_slider,\n",
    "                     bottom_left=max_slider, \n",
    "                     bottom_right=fig,\n",
    "                     align_items=\"center\", \n",
    "                     height='400px')\n",
    "\n",
    "jslink((y_sc, 'max'), (max_slider, 'value'))\n",
    "jslink((y_sc, 'min'), (min_slider, 'value'))\n",
    "jslink((min_slider, 'max'), (max_slider, 'value'))\n",
    "jslink((max_slider, 'min'), (min_slider, 'value'))\n",
    "\n",
    "max_slider.value = 1.5\n",
    "bqfig"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## AppLayout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`AppLayout` is a widget layout template that allows you to create an application-like widget arrangements. It consists of a header, a footer, two sidebars and a central pane:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import AppLayout, Button, Layout"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "header_button = create_expanded_button('Header', 'success')\n",
    "left_button = create_expanded_button('Left', 'info')\n",
    "center_button = create_expanded_button('Center', 'warning')\n",
    "right_button = create_expanded_button('Right', 'info')\n",
    "footer_button = create_expanded_button('Footer', 'success')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However with the automatic merging feature, it's possible to achieve many other layouts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=None,\n",
    "          footer=footer_button)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise\n",
    "\n",
    "In the cell below make an `AppLayout` with no sidebars."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/applayout-no-sides.py\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also modify the relative and absolute widths and heights of the panes using `pane_widths` and `pane_heights` arguments. Both accept a sequence of three elements, each of which is either an integer (equivalent to the weight given to the row/column) or a string in the format `'1fr'` (denoting one portion of the free space available) or `'100px'` (absolute size)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app = AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button)\n",
    "app"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.pane_widths = ['200px', 3, 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.pane_widths = ['200px', '3fr', '1fr']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.pane_heights = ['100px', 5, 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app.left_sidebar.description = 'New Left'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AppLayout(header=header_button,\n",
    "          left_sidebar=left_button,\n",
    "          center=center_button,\n",
    "          right_sidebar=right_button,\n",
    "          footer=footer_button,\n",
    "          pane_widths=[3, 3, 1],\n",
    "          pane_heights=[1, 5, '60px'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise\n",
    "\n",
    "Make an `AppLayout` in which there is a header, but no footer or right sidebar. Make the center the [bqplot with slider demo above](#bqplot-Figure-with-linked-sliders)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/slider-bqplot-sliders-app.py\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For an additional challenge, make the header button reset the sliders to their original position by using an event handler.\n",
    "\n",
    "## GridspecLayout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`GridspecLayout` is an M-by-N grid layout allowing for flexible layout definitions using an API similar to matplotlib's [GridSpec](https://matplotlib.org/tutorials/intermediate/gridspec.html#sphx-glr-tutorials-intermediate-gridspec-py).\n",
    "\n",
    "You can use `GridspecLayout` to define a simple regularly-spaced grid. For example, to create a 4x3 layout:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**M x N grid of buttons**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import GridspecLayout\n",
    "\n",
    "grid = GridspecLayout(4, 3)\n",
    "\n",
    "for i in range(4):\n",
    "    for j in range(3):\n",
    "        grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')\n",
    "grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Spanning range of columns and/or rows**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To make a widget span several columns and/or rows, you can use slice notation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = GridspecLayout(4, 3)\n",
    "grid[:3, 1:] = create_expanded_button('One', 'success')\n",
    "grid[:, 0] = create_expanded_button('Two', 'info')\n",
    "grid[3, 1] = create_expanded_button('Three', 'warning')\n",
    "grid[3, 2] = create_expanded_button('Four', 'danger')\n",
    "grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can still change properties of the widgets stored in the grid, using the same indexing notation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[0, 0].description = \"I am the blue one\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note**: It's enough to pass an index of one of the grid cells occupied by the widget of interest."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[3, 1] = create_expanded_button('New button!!', 'danger')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[2, 2].description = 'A better label'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Chart grid with scatter plots and histograms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this example, we will demonstrate how to use `GridspecLayout` and `bqplot` widget to create a multipanel scatter plot. To run this example you will need to install the [bqplot](https://github.com/bqplot/bqplot) package."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import bqplot as bq\n",
    "import numpy as np\n",
    "from ipywidgets import GridspecLayout, Button, Layout\n",
    "\n",
    "n_features = 3\n",
    "data = np.random.randn(100, n_features)\n",
    "data[:50, 2] += 4 * data[:50, 0] **2\n",
    "data[50:, :] += 4\n",
    "\n",
    "A = np.random.randn(n_features, n_features)/5\n",
    "\n",
    "data = np.dot(data,A)\n",
    "\n",
    "scales_x = [bq.LinearScale() for i in range(n_features)]\n",
    "scales_y = [bq.LinearScale() for i in range(n_features)]\n",
    "\n",
    "gs = GridspecLayout(n_features, n_features)\n",
    "for i in range(n_features):\n",
    "    for j in range(n_features):\n",
    "        \n",
    "        if i != j:\n",
    "            sc_x = scales_x[j]\n",
    "            sc_y = scales_y[i]\n",
    "\n",
    "            scatt = bq.Scatter(x=data[:, j], y=data[:, i], scales={'x': sc_x, 'y': sc_y})\n",
    "\n",
    "            gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),\n",
    "                                 fig_margin=dict(top=5, bottom=5, left=5, right=5), background_style={'fill':'#f5f5ff'})\n",
    "        else:\n",
    "            sc_x = scales_x[j]\n",
    "            sc_y = bq.LinearScale()\n",
    "        \n",
    "            hist = bq.Hist(sample=data[:,i], scales={'sample': sc_x, 'count': sc_y})\n",
    "            \n",
    "            gs[i, j] = bq.Figure(marks=[hist], layout=Layout(width='auto', height='auto'),\n",
    "                                 fig_margin=dict(top=5, bottom=5, left=5, right=5), background_style={'fill':'#f5f5ff'})\n",
    "gs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## GridBox Examples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3x3 grid with custom row & column sizes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Button, GridBox, Layout, ButtonStyle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "GridBox(children=[Button(description=str(i), layout=Layout(width='auto', height='auto'),\n",
    "                         style=ButtonStyle(button_color='darkseagreen')) for i in range(7)\n",
    "                 ],\n",
    "        layout=Layout(\n",
    "            width='50%',\n",
    "            grid_template_columns='100px 50px 100px',\n",
    "            grid_template_rows='80px auto 80px', \n",
    "            grid_gap='5px 10px')\n",
    "       )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Add more buttons**\n",
    "\n",
    "Modify the code above to place more buttons in the `GridBox` (do *not* modify the layout). Any number of buttons larger than 9 is fine.\n",
    "\n",
    "1. What happens to the extra buttons? Are they laid out like the first 9 buttons?\n",
    "\n",
    "The grid template defines a 3x3 grid. If additional children are placed in the grid their properties are determined by the layout properties `grid_auto_columns`, `grid_auto_rows` and `grid_auto_flow` properties.\n",
    "\n",
    "2. Set `grid_auto_rows=\"10px\"` and rerun the example with more than 9 buttons.\n",
    "\n",
    "3. Set `grid_auto_rows` so that the automatically added rows have the same format as the templated rows."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## An alternate way of defining the grid, using grid areas\n",
    "\n",
    "The grid can also be set up using a description words. The layout below defines a grid with 4 columns and 3 rows. The first row is a header, the bottom row is a footer, and the middle row has content in the first two columns, then an empty cell, followed by a sidebar.\n",
    "\n",
    "Widgets are assigned to each of these areas by setting the widgets's layout `grid_area` to the name of the area.\n",
    "\n",
    "```\n",
    "    \"header header header header\"\n",
    "    \"main main . sidebar \"\n",
    "    \"footer footer footer footer\"\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "header  = Button(description='Header',\n",
    "                 layout=Layout(width='auto', height='auto', grid_area='header'),\n",
    "                 style=ButtonStyle(button_color='lightblue'))\n",
    "main    = Button(description='Main',\n",
    "                 layout=Layout(width='auto', height='auto', grid_area='main'),\n",
    "                 style=ButtonStyle(button_color='moccasin'))\n",
    "sidebar = Button(description='Sidebar',\n",
    "                 layout=Layout(width='auto', height='auto', grid_area='sidebar'),\n",
    "                 style=ButtonStyle(button_color='salmon'))\n",
    "footer  = Button(description='Footer',\n",
    "                 layout=Layout(width='auto', height='auto', grid_area='footer'),\n",
    "                 style=ButtonStyle(button_color='olive'))\n",
    "\n",
    "GridBox(children=[header, main, sidebar, footer],\n",
    "        layout=Layout(\n",
    "            width='50%',\n",
    "            align_items='stretch',\n",
    "            grid_template_rows='auto auto auto',\n",
    "            grid_template_columns='25% 25% 25% 25%',\n",
    "            grid_template_areas='''\n",
    "            \"header header header header\"\n",
    "            \"main main . sidebar\"\n",
    "            \"footer footer footer footer\"\n",
    "            ''')\n",
    "       )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Make the main area larger\n",
    "\n",
    "1. Add another row or two to the template area so that the main area is 3 rows high and 2 columns wide.\n",
    "\n",
    "\n",
    "<!--NAVIGATION-->\n",
    "< [Layout and Styling of Jupyter widgets](06.00-Layout-and-Styling-Overview.ipynb) | [Contents](00.00-index.ipynb) | [OPTIONAL - Widget label styling](06.02-OPTIONAL-widget-label-styling.ipynb) >"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "formats": "ipynb,md:myst"
  },
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
